package org.datascooter.impl;

import java.util.HashMap;
import java.util.Map;

import org.datascooter.bundle.DSMapper;
import org.datascooter.bundle.EntityBundle;
import org.datascooter.bundle.attribute.PersistAttribute;
import org.datascooter.exception.DataScooterException;
import org.datascooter.exception.EntityNotMappedException;
import org.datascooter.utils.SnipUtils;
import org.datascooter.utils.policy.SnipType;

/**
 * The DOT notation is required everywhere. Spaces are required in queries whenever you want from datascooter to recognize and replace this part of string
 * 
 * Examples:
 * 
 * SELECT c.* FROM c ...setReturnType(Cat.class) ...addAlias("c", Class Cat.class)
 * 
 * @author user
 * 
 */
public class DQuery {

	private static final String SELECT = "SELECT * ";
	private String query;
	private Object[] paramArray;
	private Map<String, String> aliasMap = new HashMap<String, String>();
	private Object returnType;
	private String transactionId;
	private SnipType type;
	private StringBuilder builder = new StringBuilder();


	/**
	 * The DOT notation is required everywhere. Spaces are required in queries whenever you want from datascooter to recognize and replace this part of string
	 * 
	 * @param returnType
	 * @param query
	 * @param transactionId
	 * @param paramArray
	 */
	public DQuery(Object returnType, String query, String transactionId, Object... paramArray) {
		this.query = query;
		this.paramArray = paramArray;
		this.returnType = returnType;
		this.transactionId = transactionId;
		type = SnipType.SELECT;
	}

	public DQuery(Object returnType, String query, String transactionId) {
		this.query = query;
		this.returnType = returnType;
		this.transactionId = transactionId;
		type = SnipType.SELECT;
	}

	public DQuery(Object returnType, String query) {
		this.query = query;
		this.returnType = returnType;
		type = SnipType.SELECT;
	}

	public DQuery(String query) {
		this.query = query;
	}

	public DQuery(SnipType snipType, String query, String transactionId, Object... paramArray) {
		this.query = query;
		this.paramArray = paramArray;
		this.type = snipType;
		this.transactionId = transactionId;
	}

	public DQuery(SnipType snipType, String query, String transactionId) {
		this.query = query;
		this.type = snipType;
		this.transactionId = transactionId;
	}

	public DQuery(SnipType snipType, String query) {
		this.query = query;
		this.type = snipType;
	}

	/**
	 * The DOT notation is required everywhere.
	 * 
	 * Examples:
	 * 
	 * SELECT c.* FROM c ...setReturnType(Cat.class) ...addAlias("c", Class Cat.class)
	 */
	public DQuery setQuery(String query) {
		this.query = query;
		return this;
	}

	/**
	 * The name - string as in query bordered by spaces or space and a dot, class - desired entity name it means on the right side of dot - attribute of this entity . The DOT notation is required
	 * everywhere.
	 * 
	 * @param name
	 * @param clazz
	 */
	public DQuery addAlias(String name, Class<?> clazz) {
		aliasMap.put(name, clazz.getName());
		return this;
	}

	public DQuery setReturnType(Object returnType) {
		this.returnType = returnType;
		return this;
	}

	public DQuery setParams(Object... params) {
		this.paramArray = params;
		return this;
	}

	public Snip build() throws SecurityException, EntityNotMappedException, NoSuchMethodException {
		String[] split = query.split(SnipUtils.SP);
		if (type == null) {
			type = defineType(type, split[0].trim());
		}
		if (split[0].trim().equalsIgnoreCase(SnipUtils.FROM)) {
			builder.append(SELECT);
		}
		for (int a = 0; a < split.length; a++) {
			builder.append(SnipUtils.SP);
			String str = split[a];
			int indexOf = str.indexOf('.');
			if (indexOf > 0) {
				String alias = str.substring(0, indexOf).trim();
				String attr = str.substring(indexOf + 1).trim();
				String entity = aliasMap.get(alias);
				if (entity != null) {
					EntityBundle bundle = DSMapper.getBundle(entity);
					if (bundle == null) {
						throw new DataScooterException("***Bundle not found for entity:  " + entity);
					}
					builder.append(bundle.getTableName());
					builder.append(SnipUtils.DOT);
					if (attr.equals(SnipUtils.ASTERICS)) {
						builder.append(attr);
					} else {
						PersistAttribute attribute = bundle.getAttribute(attr);
						if (attribute == null) {
							throw new DataScooterException("***Attribute " + attr + "  not found in bundle of " + bundle.entity);
						}
						builder.append(attribute.getColumnName());
					}
				} else {
					builder.append(str);
				}
			} else {
				String entity = aliasMap.get(str);
				if (entity != null) {
					EntityBundle bundle = DSMapper.getBundle(entity);
					if (bundle == null) {
						throw new DataScooterException("***Bundle not found for entity:  " + entity);
					}
					builder.append(bundle.getTableName());
				} else {
					builder.append(str);
				}
			}
		}
		Object[][] params2 = null;
		if (paramArray != null) {
			params2 = new Object[][] {
				paramArray
			};
		}
		String[] fields = new String[0];
		String entity = null;
		if (type.equals(SnipType.SELECT) && returnType != null) {
			EntityBundle bundle = DSMapper.getBundle(returnType);
			if (bundle == null) {
				throw new DataScooterException("***Bundle not found for entity:  " + entity);
			}
			entity = bundle.entity;
			fields = bundle.buildColumnsInt(type);
		}
		return new Snip(entity, type, builder.toString(), params2, fields, null, -1, transactionId);
	}

	private SnipType defineType(SnipType type, String firsToken) {
		if (firsToken.equalsIgnoreCase(SnipType.SELECT.name())) {
			type = SnipType.SELECT;
		} else if (firsToken.equalsIgnoreCase(SnipUtils.FROM)) {
			type = SnipType.SELECT;
			builder.append(SELECT);
		} else if (firsToken.equalsIgnoreCase(SnipType.CREATE.name())) {
			type = SnipType.CREATE;
		} else if (firsToken.equalsIgnoreCase(SnipType.DELETE.name())) {
			type = SnipType.DELETE;
		} else if (firsToken.equalsIgnoreCase(SnipType.DROP.name())) {
			type = SnipType.DROP;
		} else if (firsToken.equalsIgnoreCase(SnipType.INSERT.name())) {
			type = SnipType.INSERT;
		} else if (firsToken.equalsIgnoreCase(SnipType.UPDATE.name())) {
			type = SnipType.UPDATE;
		}
		return type;
	}

	public DQuery setTransactionId(String transactionId) {
		this.transactionId = transactionId;
		return this;
	}

	public void setType(SnipType type) {
		this.type = type;
	}
}
